本教程并不是零基础新手教程,但是有点基础的新手确实可以看。虽然直接选择章节看是一种高效快速的阅读方法,但是如果前面说过的内容,后面就不会说太细,所以还会建议通篇大致看看。
阅读需求:
懂得如何编译运行C++代码,懂得变量命名规范,以及太过基础的知识。
明白代码中不要乱用全角字符,例如中文括号。
一颗愿意学习东西的,热情的心。
大的功能向下拆分,直到形成最小单元,再逐个实现。
打开代码编辑器输入:
// 导入头文件int main(void) { // 主函数 using namespace std; // 使用命名空间 cout << "Hello, world!" << endl; //输出内容和空行 return 0;}一个C++程序必须有main函数,否则会报错 Undefined symbol: _main ,#include <iostream> 是可选的,一个C++程序可以只有main函数,但是std命名空间(namespace)就不能使用了,它在iostream这个头文件里面。
关于头文件,在C语言中,头文件会写上扩展名,例如 stdio.h , string.h 等等。但是在C++中,头文件的扩展名被省略了,在古老的C++版本中,例如一个1987年的C++编译器,里面使用 iostream.h ,也就是旧式C++。来自C语言的头文件(转换后的C)前面会加个C,例如来自C语言的 math.h 在C++中变成了 cmath 。C++新式风格是没有头文件扩展名了。
C中头文件扩展名五花八门,有hxx、hpp,或者h,最后ANSI/ISO委员会觉得太乱套了,最后一致同意不使用任何扩展名。
main函数又叫做主函数。 int main(void) {xxx; return 0;} 是main()函数,其中 int main(void) 叫做函数头,后面的 {xxx; return 0;} 叫做函数体。
int (数据类型) 表示main函数的返回类型是整数类型(integer),return 0; 表示函数的返回值是0. (void) 表明函数不接受任何参数,括号中写的是函数接受的参数,void在英文中的解释是“空的”、“空白的”、“空虚感”。
如果想让函数接受浮点数(数据类型叫做float,可以理解为小数,因计算机中处理浮点数的方式得名“浮点”),并且函数的名字叫做fun,接受的参数是一个整数类型 int ,参数的名字叫做num,返回值是1,代码如下:
xxxxxxxxxx// 你可能会尝试把这个代码直接编译,奇怪为什么会报错,先别急,C++程序是有结构的。float fun(int num){ return 1.0;}再举两个例子:
xxxxxxxxxxint fun2(int num2){ return 0;}xxxxxxxxxxint fff(){ // 括号内可以留空,同样表示不接受参数(注意是不接受参数,不是忽略参数)。 return 0;}C++ 11新标准:
xxxxxxxxxxint f2r() = delete;这是显式表示不定义参数,表示禁止调用该函数。
用两个斜线,后面一个空格表示注释。双斜线后面的代码(Code)都不会被执行,直到换行。
例如下面的程序:
xxxxxxxxxxint plus(int a, int b) { // 双斜线后面的内容不会被执行 a=a+1; // a=1; // b=2; return a+b;}第一行 (int a, int b) 表示这个函数接受两个参数,注意看这两个参数中间用逗号连接。
注释可以用来标记解释代码中的功能,写程序要养成写注释的好习惯,免得你以后看自己以前的代码,或者在公司里你的同事看你的代码一头雾水。
第三行 a=a+1 是一个“赋值运算”。对,它是一种运算。这句代码的等号就是赋值运算符,在赋值运算中,优先计算赋值运算符右侧的内容,也就是 a+1 ,然后轮到左侧,a 增大1的结果被赋值给 a 本身,所以 a 的熟知被增大1. 例如调用函数(函数英文叫做function,我在下面简单介绍)的时候
函数(function),function在英文中是功能的意思,也就是函数可以实现一个功能。一个很推荐的程序设计思想叫做“模块化”、“结构化编程”,大的功能向下拆分,直到形成最小单元,再逐个实现。
上面展示的函数的作用和它的名字一样,“加”(plus)。调用函数的时候给函数塞两个整数参数进去,它就能返回相加的结果。
xxxxxxxxxxint result; // 存储运算结果result = plus(1, 2); // 调用plus函数result 的结果会是3.
还记得小学学的字母表示数吗?你还可以这样调用:
xxxxxxxxxxint a=1;int b=2;// 或者这样写:int a = 1, b = 2;result = plus(a, b);不知不觉说多了,更多内容在函数章节细讲。
C++有很多种语句,函数也是语句,前面还说过赋值语句,下面说一下声明变量。
变量是给一块内存空间取的名字,用来存储和操作数据。这块空间有多大和变量类型有关,不同操作系统不同编译器上变量类型占用空间可能有差别。MS-DOS Turbo C++ 3.0上的char是1字节(byte)。1Byte=1/1024KB,B, KB, MB, GB, TB都是这个换算规律。
举例几个合规的变量名:使用有意义的、以小写字母开头的单词组合,单词间可用下划线分隔或驼峰式命名,避免使用保留关键字(例如int、char、void和return等)和数字开头。关键字是C++专用的词语,不能用做其他用途,你不能把int作为函数名,也不能把void作为变量名。
蛇形命名法:user_name、total_count
驼峰命名法:userName、totalCount
常量:MAX_SIZE
类名:ClassName
变量要有类型,没有一个默认的数据,程序只会给它分配一个空闲的数据块,但是不保证里面是空的,可能有其他程序或者内容留下的垃圾内容,所以你在读取它之前应该赋值,或者给它一个默认值。给予默认值的操作叫做“初始化”。在C++中变量在使用之前需要先声明,其他语言例如Python不需要这样做,使用前需要先声明这个机制好就好在你不会因为笔误不小心造一个变量出来,而且自己还看不出来,例如下面的BASIC代码:
xxxxxxxxxxCastleDark = 34CastleDank = CasleDark + MoreGhostsPRINT CastleDark上面的代码意图是把值为34的变量CastleDark增加MoreGhosts,然后输出MoreGhosts的数值。然而第二行不小心写错个字母,一眼看上去还看不出来,这样CastleDark的值永远是34。
实际第二行做的工作是新建个变量CastleDank,给它加上MoreGhosts。CastleDark的值还是34,没有被修改。
看下面代码演示变量相关操作。
xxxxxxxxxxint a; // 声明变量int b = 0; // 声明变量的同时初始化为0;如果你想知道不初始化直接读取变量会发生什么,请看下面程序:
x
int main(void){ int a; std::cout << a << std::endl; return 0;}我在 macOS Tahoe 26.1 操作系统上,Xcode 26.1.1 (17B100) 集成开发环境,Apple clang version 17.0.0 (clang-1700.4.4.1) 编译器,得到的结果如下:
xxxxxxxxxx-278069152Program ended with exit code: 0
可以看出这确实是个杂乱无章的数值。
函数用于创建C++程序的模块,与面向对象编程有关,高级内容后面细说。
函数接受和传入返回值的方法前面“C++注释”章节已经说过了一些,下面再用例子演示函数多个参数的传入:
xxxxxxxxxx// 函数头int plus(int a, int b, int c); // 此为函数头,也可以简写为int plus(int, int, int);// 主函数int main(void){ int result; int num1 = 2; int num2 = 3; int num3 = 6; result = plus(num1, num2, num3); std::cout << "Result: " << result << std::endl; // 双引号中间的部分是字符串,可以试试自己修改成别的。 return 0; // 结束main()函数,返回值为0,但实际上现代C++中可以省略。}// 函数定义int plus(int a, int b, int c){ return a+b+c;}
运行结果:
xxxxxxxxxxResult: 11Program ended with exit code: 0
如果你在主函数(main函数)里面加上 using namespace std; 代码会报错,因为std名称空间中有同名的、也叫plus的内容,这引起了冲突。如果非要使用这个代码,就要把plus函数换个别的名字,例如 plus1 。
第二个例子:
xxxxxxxxxx// 显示文字void print(std::string); // string是字符串类型,两个冒号不需要纠结,以后详细说,只需要知道string属于std命名空间。// 重复字符串的内容std::string duplicate(std::string);// 重复字符串并打印void dup_and_print(std::string);
int main(int argc, char* argv[]){ using namespace std; string str1 = "Hello"; // string前面不需要std::是因为前面前面引入了std命名空间 str1 = duplicate(str1); // 重复str1 dup_and_print(str1); // 重复str1并向命令行输出文本}
void print(std::string str){ // using namespace std; // 把这行的注释去掉以后,下面的 std:: 就可以去掉了。 // 不使用 using namespace std; 就要使用std::cout,因为你要手动指定自己在使用这个命名空间中的cout。 std::cout << str << std::endl; return; // 什么也不返回,只是结束函数,你也可以省略这一行}
std::string duplicate(std::string str){ return str + str; // 把两个字符串连接起来}
void dup_and_print(std::string strrr){ using namespace std; // 函数中可以调用别的函数,层层组合,一个函数指挥多个函数协同运作,成为更强大的顶层的函数。 strrr = duplicate(strrr); cout << strrr << endl;}
运行结果:
xxxxxxxxxxHelloHelloHelloHelloProgram ended with exit code: 0
你可能会疑惑,上面的代码中为什么多个地方在使用 str 这个名字,这样是否会导致冲突?
其实并不会,C和C++有“作用域”的概念,作用域就是程序中变量/标识符的有效区域,决定了在哪里可以访问它。下面用几个例子讲解:
局部作用域(函数内):
xxxxxxxxxxvoid func() { int x = 10; // x只在func内有效 // x可以在这里使用}// x在这里不可访问块作用域:
xxxxxxxxxx{ int y = 20; // y只在这个{}内有效}// y在这里不可访问
if (true) { int z = 30; // 块作用域}如果你把变量/标识符放在任何块和局部作用域外面,那就是全局变量:
xxxxxxxxxxint a = 3; // a在全局作用域void visiablefunc(int b); // 同样,这个函数的位置也在全局作用域,任何地方都可见int main(void){ int c = 2; std::cout << a; // a在任何地方都可见 visiablefunc(a);}void visiablefunc(int b){ std::cout << a; // std::cout << c; // 这会导致报错,因为c只在main函数里面可见。}
命名空间作用域:
xxxxxxxxxxnamespace myspace { // 自定义一个命名空间// 函数只在这个命名空间内可见int plus(int a, int b, int c){ return a+b+c;}}int main(void){ using namespace std; int result; int num1 = 2; int num2 = 3; int num3 = 6; // plus函数的返回值被赋值给result result = myspace::plus(num1, num2, num3); // 用myspace::来访问plus函数 cout << "Result: " << result << endl; return 0;}
using namespace std; 引入了命名空间std,如果直接使用plus,程序会报错 No viable constructor or deduction guide for deduction of template arguments of 'plus' ,因为std中有叫做plus的东西了,准确说这个“东西”是函数对象类,它是这么用的 std::plus<int> add; ,虽然不是个函数,却占用了plus这个名字。这时候名称空间就发挥了作用,我们自己定义的plus是个函数,被我们放在mysapce命名空间里面,我们使用的时候用 myspace::plus(xxx) 就能调用plus函数,而且不引起冲突,因为myspace里面只有一个叫做plus的东西。
前面简单提过一点变量(Variable)的事情,它被分配到内存上的一块空间,我们使用变量名来向这块空间中存储数据,或者读取其中的数据。
在实际项目中变量名最好用有意义的名字,尽量不要用 a 、 var1 、 ffff 这种名字,result 甚至 jie_guo 都好很多,配合上注释,能快速清晰的了解程序。
整数类型有 short, int, long, long long 四种,int 适合多数情况的数字存储。C++确保这些类型的最小长度,short至少16位,int 至少和 short 一样长,long 至少32位,至少与 int 一样长,long long 至少64位,至少与 long 一样长。
字符类型是 char ,可以存储一个ASCII字符,ASCII字符都有什么可以自己上网查询,里面包括字母、常见特殊符号。
整数类型和字符类型的运用示例代码如下:
xxxxxxxxxx// 使用printf()需要引入这个头文件
int main(void){ char ch = 'a'; // 存储一个字符 char ch2 = 'b'; // 下面这个字符串在数组中实际的内容其实是'H' 'e' 'l' 'l' 'o' '\0' // 它之所以需要6个字符的空间是因为字符串"Hello"的结尾还有一个空字符'\0' char str[6] = "Hello"; // 通过声明一个字符数组来存储字符串 int number1 = 8; int num2 = 6; std::cout << "ch: " << ch << ", ch2:" << ch2 << std::endl; std::cout << str << std::endl; std::cout << "8+6=" << number1 + num2 << std::endl; std::cout << "8-6=" << number1 - num2 << std::endl; // char str[6]是C语言风格声明字符串,在C++中这样的字符串可以直接赋值给std命名空间中的string类型。 std::string str2 = str; std::cout << "str1: " << str << ", str2: " << str2 << std::endl; printf("str1: %s, str2: %s \n", str, str2.c_str()); // 如果你是从C语言来的,或者喜欢直观一点,可以试试这个。 // %s是字符串的占位符,函数执行的时候会按顺序替换成你后面用逗号分隔开的字符串变量,\n是换行符。 // printf是C语言的东西,str2是C++中的std::string类型,C语言的printf不认识,所以要用str2.c_str()转成C风格字符串 // printf是print format的缩写,占位符除了字符串占位符%s,还有%d, %c等,更多内容可以自己查询。}
不管是整数类型还是浮点类型、字符类型(char),都有一个最大值,我们可以查看一些符号常量来得知。看下面代码:
xxxxxxxxxx
void show(int);void show_l(long long); // 注意是(long long),不是(long, long),long long 是一个整体,类型。
int main(void){ show(CHAR_BIT); // char 字符的位数 show(CHAR_MAX); // char 字符的最大值 show(CHAR_MIN); // MAX是最大值,MIN是最小值,下面内容也是如此。 show(SCHAR_MAX); // signed char 有符号字符 show(SCHAR_MIN); show(UCHAR_MAX); // usigned char 无符号字符 show(SHRT_MAX); // short 短整数 show(SHRT_MIN); show(USHRT_MAX); // unsigned short 无符号短整数 show(INT_MAX); // int 整数 show(INT_MIN); // 从UINT_MAX开始,数字就很大,超出了int的存储范围,所以我用了第二个函数。 show_l(UINT_MAX); // unsigned int 无符号整数 show_l(LONG_MAX); // long 长整数 show_l(LONG_MIN); show_l(ULONG_MAX); // unsigned long 无符号长整数(32bit) show_l(LLONG_MAX); // long long 64位的长整数 show_l(LLONG_MIN); show_l(ULLONG_MAX); // unsigned long long 无符号长长整数}
void show(int size){ std::cout << size << std::endl;}
void show_l(long long size){ std::cout << size << std::endl;}
变量的位数是有最大限度的,它总共就那么长,想表示正负数就肯定损失最大值,例如short类型范围可能(不同编译器和操作系统上可能有区别)是-32767到+32767,无符号的short就能表示0到65535,想让变量无符号,只需要在前面加个 unsigned .
变量不初始化(赋值),数值是不确定的,这个前面代码演示过。
char 不光能存储字符,还能存储数字,或者说 char 在存储字符的时候本来就是存储了一个字符对应的ASCII或者UTF8码。例如你要存储256色图像,就可以声明一个 unsigned char 类型的数组(Array,有顺序的存储一个或多个同一个类型的数据)来存储图像。
有些字符不是显示出来的,而是起到辅助作用的,例如换行符号,下面代码演示了换行符的作用:
xxxxxxxxxx
int main(void){ std::cout << "Hello\nworld!" << std::endl; std::cout << "\'Hello world!\'" << std::endl; std::cout << "\"Hello world!\"" << std::endl;}
程序输出结果如下:
xxxxxxxxxxHelloworld!'Hello world!'"Hello world!"Program ended with exit code: 0换行符几乎是最常用的符号之一,振铃BEL在现在的设备上不一定有用,如果你用的是一些Linux发行版的默认虚拟终端或者macOS系统虚拟终端,在执行程序的时候通常可以听到提示音或者窗口闪烁。问号一般不需要转义,直接用就行,反斜杠、单引号、双引号都有特殊的作用,转义符号、包装字符、包装字符串,所以如果你想把这些字符显示出来,就需要转义它们。
上面代码可以看到换行符号、转义后的单双引号发挥了作用,下表列举了一些特殊符号的ASCII符号、十六进制ASCII码对应关系:
| Char name | ASCII character | C++ code | HEX ASCII code |
|---|---|---|---|
| 换行符 | NL(LF) | \n | 0xA |
| 水平制表符 | HT | \t | 0x9 |
| 垂直制表符 | VT | \v | 0xB |
| 退格 | BS | \b | 0x8 |
| 回车 | CR | \r | 0xD |
| 振铃 | BEL | \a | 0x7 |
| 反斜杠 | \ | \\ | 0x5C |
| 问号 | ? | \? | 0x3F |
| 单引号 | ' | \' | 0x27 |
| 双引号 | " | \" | 0x22 |
C++总共两种浮点数表示方法,一种是普通的小数表示法,另一种是中学学过的科学计数法。
xxxxxxxxxx12.12 // 双12快到了273.155.0// 科学计数法1.23E2 // 1.23 * 10^2(1.23乘以10的2次幂)1E5 // 1 * 10^51.0E+5 // 1.0 * 10^5+1.89E-6
科学计数法:数字开头可以正号负号或者省略,E后面是10的几次方,E的前后都没有空格,E的后面的数字可以有正号、负号或者省略(等同于正号)。
物理学上也经常使用科学计数法,例如电子质量是9.11e-31kg,物理学上写作9.11 * 10^31(我写公式要启用LaTeX支持,所以直接用计算器的风格写了,应该能看懂)。